/*    
 * Copyright (c) 2013 ISoftStone, Inc.
 * All right reserved.
 *
 * 文件名：      AccountingServiceImpl.java
 * 作者:     Jacky Wang
 * 创建日期： 2013-7-29 下午01:22:51
 * 版本：           
 *
 */
package com.isoftstone.industry.aaa.core.service.accounting.impl;

import java.io.File;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.isoftstone.industry.common.util.SNGenerator;
import com.isoftstone.industry.persistance.bean.AccountItem;
import com.isoftstone.industry.persistance.bean.AccountItemType;
import com.isoftstone.industry.persistance.bean.Bill;
import com.isoftstone.industry.persistance.bean.BillCycleConfig;
import com.isoftstone.industry.persistance.bean.BillItem;
import com.isoftstone.industry.persistance.bean.BillItemAcctItem;
import com.isoftstone.industry.persistance.bean.BillingCycle;
import com.isoftstone.industry.persistance.bean.BillingList;
import com.isoftstone.industry.persistance.bean.ChargingItemDetail;
import com.isoftstone.industry.persistance.bean.PricingInfoSnapshot;
import com.isoftstone.industry.persistance.bean.ProductInstance;
import com.isoftstone.industry.persistance.bean.User;
import com.isoftstone.industry.aaa.core.log.BusinessLogger;
import com.isoftstone.industry.aaa.core.log.ILogMessages;
import com.isoftstone.industry.aaa.core.service.accounting.IAccountingService;
import com.isoftstone.industry.aaa.core.service.accounting.IChargeOffService;
import com.isoftstone.industry.aaa.core.service.accounting.IChargingService;
import com.isoftstone.industry.aaa.core.service.assist.IAccountItemService;
import com.isoftstone.industry.aaa.core.service.assist.IBillCycleConfigService;
import com.isoftstone.industry.aaa.core.service.assist.IBillService;
import com.isoftstone.industry.aaa.core.service.assist.IBillingCycleService;
import com.isoftstone.industry.aaa.core.service.assist.IBillingCycleUserService;
import com.isoftstone.industry.aaa.core.service.assist.IBillingListService;
import com.isoftstone.industry.aaa.core.service.assist.IChargingItemService;
import com.isoftstone.industry.aaa.core.service.assist.IUserService;
import com.isoftstone.industry.aaa.core.service.email.IEmailSupportService;
import com.isoftstone.industry.aaa.exception.AAAException;
import com.isoftstone.industry.aaa.exception.IAAAErrorCode;
import com.isoftstone.industry.aaa.utils.SystemProperties;
import com.isoftstone.modules.core.utils.DateUtils;
import com.isoftstone.modules.core.utils.lock.FileProgrameLock;
import com.isoftstone.modules.core.utils.lock.Lock;

/**
 * @author jacky
 */
@Service
public class AccountingServiceImpl implements IAccountingService
{
	@Resource
	private BusinessLogger				logger;

	@Resource
	private IUserService				userService;

	@Resource
	private IBillCycleConfigService		billCycleConfigService;

	@Resource
	private IBillingCycleService		billingCycleService;

	@Resource
	private IBillService				billService;

	@Resource
	private IChargingItemService		chargingItemService;

	@Resource
	private IAccountItemService			acctItemService;

	@Resource
	private IChargeOffService			chargeOffService;

	@Resource
	private IChargingService			chargingService;

	@Resource
	private IBillingCycleUserService	billingCycleUserService;

	@Resource
	private IEmailSupportService emailSupportService;
	
	@Resource
	private IBillingListService billingListService;
	@Resource
	private SystemProperties systemProperties;
	
	private void doAccounting(User user, BillCycleConfig billCycleConfig, BillingCycle billingCycle)
	{
		if (isNeedAccounting(user, billCycleConfig, billingCycle))
		{
			Bill bill = _doAccounting(user, billingCycle);

			try
			{
				//对本周期的账单进行销帐
				if (bill != null)
				{
					chargeOffService.chargeOff(bill);
				}
			} catch (AAAException e)
			{
				//账户冻结
				if (e.getErrorCode().intern() == IAAAErrorCode.ERROR_ACCOUNT_FREEZE.intern())
				{
					emailSupportService.notifyAccountFreeze(user.getId());
				}
			}
			//通过邮件发送电子账单
			if (bill != null)
			{
				emailSupportService.notifySendBill(user.getId(), bill.getId());
			}
		}
	}

	private Bill _doAccounting(User user, BillingCycle billingCycle)
	{
		String userId = user.getId();
		//找到本周期内的帐目
		List<AccountItem> acctItems = acctItemService.findNeedAccountingAccountItems(userId, billingCycle);
		//根据产品实例归类生成账单项
		List<BillItem> billItemList = new ArrayList<BillItem>(0);
		double amount = 0L;
		double paid = 0L;
		for (AccountItem acctItem : acctItems)
		{
			acctItem.setBillingCycle(billingCycle);
			findOrCreateBillItem(billItemList, acctItem);
			amount += acctItem.getAmount();
			paid += acctItem.getPaidAmount();
		}
		//创建账单
		Bill bill = createBill(user, billItemList, amount,paid, billingCycle);
		logger.info(ILogMessages.ACCOUNTING_CREATE_BILL, user.getUsername(), billingCycle.getName(), amount);
		return billService.save(bill);
	}

	/**
	 * 是否需要进行出帐
	 * 
	 * @param
	 * @return
	 * @throws
	 */
	private boolean isNeedAccounting(User user, BillCycleConfig billCycleConfig, BillingCycle billingCycle)
	{
		/**
		 * 符合以下条件，则可以进行出帐: 1. 该帐务周期内还没有出过帐 2. 该用户已经到了出帐周期 3.
		 * 在该帐务周期内所有计费子项都已经计费完成
		 */
		String userId = user.getId();
		//已存在账单，说明已经出过帐了
		if (billService.hasBillByUserAndBillingCycle(userId, billingCycle.getId()))
		{
			logger.info(ILogMessages.ACCOUNTING_EXIST4USER_HASBILL, user.getUsername(), billingCycle.getName());
			return false;
		}
		//判断当天是否已经到了出帐期
		Date today = DateUtils.today();
		Date firstDay = DateUtils.getFirstDayOfMonth(today);
		Date accountingDate = DateUtils.adjustByDay(firstDay, billCycleConfig.getStartDay() - 1);
		if (today.before(accountingDate))
		{
			logger.info(ILogMessages.ACCOUNTING_EXIST4USER_NOREACHACCOUNTINGDAY, user.getUsername(), billingCycle.getName(), DateUtils.date2String(accountingDate, DateUtils.YYYY_MM_DD_PATTERN));
			return false;
		}
		//判断所有计费子项是否已经全部计费完成
		long count = chargingItemService.countNeedChargeItemsByUserInBillingCycle(userId, billingCycle);
		if (count > 0)
		{
			logger.info(ILogMessages.ACCOUNTING_EXIST4USER_EXISTCHARGEITEM, user.getUsername(), billingCycle.getName());
			return false;
		}
		return true;
	}

	private BillItem findOrCreateBillItem(List<BillItem> billItemList, AccountItem acctItem)
	{
		BillItem result = null;
		for (BillItem billItem : billItemList)
		{
			if (billItem.getProdInst().getId().equals(acctItem.getProductInst().getId()))
			{
				result = billItem;
				break;
			}
		}
		if (result == null)
		{
			result = new BillItem();
			result.setProdInst(acctItem.getProductInst());
			result.setPrintOrder(billItemList.size() + 1);
			result.setCreatedTime(DateUtils.getCurrentTimestamp());
			billItemList.add(result);
		}
		BillItemAcctItem baItem = new BillItemAcctItem();
		baItem.setAccountItem(acctItem);
		result.addBillItemAcctItem(baItem);
		return result;
	}

	private Bill createBill(User user, List<BillItem> billItemList, double amount, double paid, BillingCycle billingCycle)
	{
		Bill bill = new Bill();

		bill.setUser(user);
		bill.setSn(SNGenerator.createBillSN());
		for (BillItem item : billItemList)
		{
			bill.addBillItem(item);
		}
		bill.setBillingCycle(billingCycle);
		bill.setAmount(amount);
		bill.setPaidAmount(paid);
		bill.setPayableAmount(amount-paid);
		bill.setBillType(Bill.Type.ELECTRONIC);
		bill.setDeliverStatus(Bill.DeliverStatus.NODELIVER);
		Timestamp now = DateUtils.getCurrentTimestamp();
		//bill.setDeliverTime(now);
		bill.setCreatedTime(now);
		return bill;
	}

	

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void timingAccounting()
	{
		logger.info(ILogMessages.ACCOUNTING_START);
		//找出所有需要进行出帐的用户列表
		List<User> userList = userService.findNeedAccountingUsers();

		for (User user : userList)
		{
			Lock lock = getFileLock(user);
			//若可以成功获取文件锁，则代表没有进程正在为该用户进行出帐
			if (lock.obtain() == true)
			{
				try{
					doAccountingForUser(user);
				}catch (Exception e){
					logger.error(e.getMessage(), e);
				}finally{
					//释放文件锁
					lock.unlock();
				}
			}else{
				logger.info(ILogMessages.ACCOUNTING_FETCH_LOCK_FAILED, user.getUsername());
			}
		}
		logger.info(ILogMessages.ACCOUNTING_END);
	}

	private Lock getFileLock(User user)
	{
		String fileName = systemProperties.getFileLockDir() + File.separator + "accounting_" + user.getId() + ".lock";
		return FileProgrameLock.get(fileName);
	}
	
	@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
	public void doAccountingForUser(User user)
	{
		//找到该用户的帐务周期配置
		BillCycleConfig billCycleConfig = billCycleConfigService.getByUser(user.getId());
		//根据该帐务周期配置找到本周期内的帐务周期
		BillingCycle billingCycle = billingCycleService.findOrCreateLastCycleByConfig(billCycleConfig);
		
		//检查该用户是否在本周期有计费的产品实例
		if(userService.hasChargingProductInst(user.getId(), billingCycle)){
			//检查该用户是否在本周期内已经出帐过了
			if(!billService.hasBillByUserAndBillingCycle(user.getId(), billingCycle.getId())){
				logger.info(ILogMessages.ACCOUNTING_START4USER, user.getUsername(), billingCycle.getName());
				//绑定帐务周期和用户的关系
				billingCycleUserService.bindUserWithBillingCycle(user.getId(), billingCycle.getId());
				//先对需要出帐的用户进行计费
				chargingService.charge4UserInBillingCycle(user, billingCycle);
				//对该用户的计费清单生成本周期内的帐目
				createAcctItem4UserInBillingCycle(user, billingCycle);
				doAccounting(user, billCycleConfig, billingCycle);
				logger.info(ILogMessages.ACCOUNTING_END4USER, user.getUsername(), billingCycle.getName());
			}else{
				logger.info(ILogMessages.ACCOUNTING_EXIST4USER_HASBILL, user.getUsername(), billingCycle.getName());
			}
		}else{
			logger.info(ILogMessages.ACCOUNTING_EXIST4USER_NOCHARGINGPRODINST, user.getUsername(), billingCycle.getName());
		}
	}
	
	/**
	 * 根据该用户的计费清单，生成本周期内的帐目
	 * @param       
	 * @return     
	 * @throws
	 */
	private void createAcctItem4UserInBillingCycle(User user, BillingCycle billingCycle)
	{
		//获得需要在本周期内出帐的计费清单
		List<BillingList> data = billingListService.findGroupListByUser(user.getId(), billingCycle);
		//计费子项ID->[帐目类型ID->计费清单列表]
		Map<String, Map<String, List<BillingList>>> map = new HashMap<String, Map<String, List<BillingList>>>();
		for (BillingList d : data)
		{
			String detailId = d.getChargingItemDetail().getId();
			String acctItemTypeId = d.getAcctItemType().getId();
			Map<String, List<BillingList>> tmp = null;
			List<BillingList> list = null;
			if (map.containsKey(detailId))
			{
				tmp = map.get(detailId);
				list = tmp.get(acctItemTypeId);
				list.add(d);
			} else
			{
				tmp = new HashMap<String, List<BillingList>>();
				list = new ArrayList<BillingList>();
				list.add(d);
			}
			tmp.put(acctItemTypeId, list);
			map.put(detailId, tmp);
		}
		
		for (Map.Entry<String, Map<String, List<BillingList>>> entry : map.entrySet())
		{
			for(Map.Entry<String, List<BillingList>> entry1 : entry.getValue().entrySet()){
				double total = 0.00;
				List<BillingList> list = entry1.getValue();
				String[] bids = new String[list.size()];
				for(int i=0; i<list.size(); i++){
					total += list.get(i).getAmount();
					bids[i] = list.get(i).getId();
				}
				AccountItemType acctItemType = new AccountItemType();
				acctItemType.setId(entry1.getKey());
				ChargingItemDetail detail = list.get(0).getChargingItemDetail();
				ProductInstance prodInst = list.get(0).getProductInstance();
				PricingInfoSnapshot pricingInfoSnapshot = detail.getPricingInfoSnapshot();
				AccountItem acctItem = acctItemService.createAccountItem(acctItemType, prodInst, total, pricingInfoSnapshot, billingCycle);
				billingListService.update4AcctItem(bids, acctItem);
			}
			
		}
		
	}
}
